iT邦幫忙

2025 iThome 鐵人賽

DAY 28
0
Modern Web

Laravel 是甚麼系列 第 28

上傳圖片

  • 分享至 

  • xImage
  •  

結果整理後,應該保留這段正確的 modal:

<!-- Modal -->
<div class="modal fade" id="upload_image_modal" tabindex="-1" role="dialog" aria-labelledby="uploadImageModalLabel" aria-hidden="true">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="uploadImageModalLabel">上傳圖片</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <form id="upload_form" method="POST" action="/admin/products/upload-image" enctype="multipart/form-data">
          @csrf
          <input type="hidden" id="product_id" name="product_id">
          <div class="form-group">
            <label for="product_image">選擇圖片</label>
            <input type="file" class="form-control" id="product_image" name="product_image">
          </div>
          <button type="submit" class="btn btn-primary">上傳</button>
        </form>
      </div>
    </div>
  </div>
</div>

✅ 補充:路由對應確認
你需要在 web.php 或相關 route 檔中加入這條對應圖片上傳的 POST 路由:

Route::post('admin/products/upload-image', 'Admin\ProductController@uploadImage');

以及控制器方法:

public function uploadImage(Request $request)
{
    $request->validate([
        'product_id' => 'required|integer|exists:products,id',
        'product_image' => 'required|image|max:2048',
    ]);

    $product = Product::findOrFail($request->product_id);

    $path = $request->file('product_image')->store('product_images', 'public');

    // 假設你有個 image_path 欄位儲存圖片路徑
    $product->image_path = $path;
    $product->save();

    return redirect()->back()->with('success', '圖片上傳成功');
}

資料庫顯示:
https://ithelp.ithome.com.tw/upload/images/20251006/20119035zoyzyNz6WT.png
資料表SQL

CREATE TABLE `images` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `attachable_type` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '來源表',
  `attachable_id` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '來源表ID',
  `path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '路徑',
  `filename` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '檔案名稱',
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NULL DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

後端
https://ithelp.ithome.com.tw/upload/images/20251006/20119035SX9GTUM5qF.png
畫面就是自動回到這頁
https://ithelp.ithome.com.tw/upload/images/20251006/20119035cUeGIeTbuS.png
程式碼


<?php

namespace App\Http\Controllers\Admin;

use Illuminate\Http\Request;
use App\Models\Product;
use App\Http\Controllers\Controller;
use App\Notifications\ProductDelivery;

class ProductController extends Controller
{
    public function index(Request $request)
{

    $productCount = Product::count();
    $dataPerPage = 2;
    $productPages = ceil($productCount / $dataPerPage);
    $currentPage = isset($request->all()['page']) ? $request->all()['page'] : 1;
    $products = Product::orderBy('created_at','desc')
                   ->offset($dataPerPage * ($currentPage - 1))
                   ->limit($dataPerPage)
                   ->get();
    
    return view('admin.products.index',['products' => $products,
    'productCount' => $productCount,
    'productPages' => $productPages]);

}

    public function uploadImage(Request $request)
{
    $request->validate([
        'product_id' => 'required|integer|exists:products,id',
        'product_image' => 'required|image|max:2048',
    ]);

    $product = Product::findOrFail($request->product_id);

    $path = $request->file('product_image')->store('product_images', 'public');

    // 假設你有個 image_path 欄位儲存圖片路徑
    $product->image_path = $path;
    $product->save();

    return redirect()->back()->with('success', '圖片上傳成功');
}

}

https://ithelp.ithome.com.tw/upload/images/20251006/20119035XL5oGWzjKy.png

程式碼

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Image extends Model
{
    protected $guarded = [];

    public function attachable()
    {
        return $this->morphTo();
    }
}

https://ithelp.ithome.com.tw/upload/images/20251006/20119035cmY6IITjwl.png

程式碼

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    use HasFactory;

    protected $guarded =[''];

    public function cartItems()
    {
        return $this->hasMany(CartItem::class);
    }
    public function orderItems()
    {
        return $this->hasMany(OrderItem::class);
    }
    public function checkQuantity($quantity)
    {
        if ($this->quantity < $quantity) {
            return false;
        }

        return true;
    }

    public function images()
    {
        return $this->morphMany(Image::class, 'attachable');
    }

}

https://ithelp.ithome.com.tw/upload/images/20251006/20119035DH7R7GbdXA.png

程式碼

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateImages extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
            Schema::create('images', function (Blueprint $table) {
                $table->id();
                $table->string('attachable_type', 255)->comment('來源表');
                $table->string('attachable_id', 255)->comment('來源表ID');
                $table->string('path', 255)->comment('路徑');
                $table->string('filename', 255)->comment('檔案名稱');
                $table->timestamps();
            });
        
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('images');
    }
}

https://ithelp.ithome.com.tw/upload/images/20251006/20119035ARptfVojdF.png

程式碼

@extends('layouts.admin_app')
@section('content')
<h2>產品列表</h2>
<span>產品總數: {{ $productCount }} </span>
<table>
  <thead>
    <tr>
      <td>編號</td>
      <td>標題</td>
      <td>內容</td>
      <td>價格</td>
      <td>數量</td>
      <td>圖片</td>
      <td>功能</td>
    </tr>
  </thead>
  <tbody>
    @foreach( $products as $product )
      <tr>
        <td>{{ $product->id }}</td>
        <td>{{ $product->title }}</td>
        <td>{{ $product->content }}</td>
        <td>{{ $product->price }}</td>
        <td>{{ $product->quantity }}</td>     
        <td></td>
        <td>
            <input type="button" class="upload_image" data-id="{{ $product->id }}" value="上傳圖片">
        </td>
      </tr>
    @endforeach
  </tbody>
</table>
<div>
  @for ($i = 1; $i <= $productPages; $i++)
      <a href="/admin/products?page={{ $i }}">第 {{ $i }} 頁</a> &nbsp;
  @endfor
</div>

<!-- Add Modal for Image Upload -->
<div class="modal fade" id="upload_image_modal" tabindex="-1" role="dialog" aria-labelledby="uploadImageModalLabel" aria-hidden="true">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="uploadImageModalLabel">上傳圖片</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <!-- <form id="upload_form" method="POST" enctype="multipart/form-data">-->
        <form id="upload_form" method="POST" action="/admin/products/upload-image" enctype="multipart/form-data">

          @csrf
          <input type="hidden" id="product_id" name="product_id">
          <div class="form-group">
            <label for="product_image">選擇圖片</label>
            <input type="file" class="form-control" id="product_image" name="product_image">
          </div>
          <button type="submit" class="btn btn-primary">上傳</button>
        </form>
      </div>
    </div>
  </div>
</div>

<script>
$(document).ready(function() {
  $('.upload_image').click(function() {
    $('#product_id').val($(this).data('id'));
    $('#upload_image_modal').modal('show');
  });
});
</script>
@endsection

https://ithelp.ithome.com.tw/upload/images/20251006/201190355ON7naarRS.png

程式碼

Route::post('admin/products/upload-image', 'Admin\ProductController@uploadImage');

https://ithelp.ithome.com.tw/upload/images/20251006/20119035LcaCwz22L7.png

程式碼

<!-- Modal -->
<div class="modal fade" id="upload_image_modal" tabindex="-1" role="dialog" aria-labelledby="uploadImageModalLabel" aria-hidden="true">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="uploadImageModalLabel">上傳圖片</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <form id="upload_form" method="POST" action="/admin/products/upload-image" enctype="multipart/form-data">
          @csrf
          <input type="hidden" id="product_id" name="product_id">
          <div class="form-group">
            <label for="product_image">選擇圖片</label>
            <input type="file" class="form-control" id="product_image" name="product_image">
          </div>
          <button type="submit" class="btn btn-primary">上傳</button>
        </form>
      </div>
    </div>
  </div>
</div>

修改 圖片 放的位置
原來的位置

修改後的位置

修改後的程式碼


<?php

namespace App\Http\Controllers\Admin;

use Illuminate\Http\Request;
use App\Models\Product;
use App\Http\Controllers\Controller;
use App\Notifications\ProductDelivery;

class ProductController extends Controller
{
    public function index(Request $request)
{

    $productCount = Product::count();
    $dataPerPage = 2;
    $productPages = ceil($productCount / $dataPerPage);
    $currentPage = isset($request->all()['page']) ? $request->all()['page'] : 1;
    $products = Product::orderBy('created_at','desc')
                   ->offset($dataPerPage * ($currentPage - 1))
                   ->limit($dataPerPage)
                   ->get();
    
    return view('admin.products.index',['products' => $products,
    'productCount' => $productCount,
    'productPages' => $productPages]);

}

    /*public function uploadImage(Request $request)
{
    $request->validate([
        'product_id' => 'required|integer|exists:products,id',
        'product_image' => 'required|image|max:2048',
    ]);

    $product = Product::findOrFail($request->product_id);

    $path = $request->file('product_image')->store('product_images', 'public');

    // 假設你有個 image_path 欄位儲存圖片路徑
    $product->image_path = $path;
    $product->save();

    return redirect()->back()->with('success', '圖片上傳成功');
}*/

public function uploadImage(Request $request)
{
    $request->validate([
        'product_id' => 'required|integer|exists:products,id',
        'product_image' => 'required|image|max:2048',
    ]);

    $product = Product::findOrFail($request->product_id);

    $path = $request->file('product_image')->store('images', 'public');
    $filename = basename($path);

    // Create a new image record in the images table instead of updating products table
    $product->images()->create([
        'path' => $path,
        'filename' => $filename,
        'attachable_type' => 'App\Models\Product',
        'attachable_id' => $product->id,
    ]);

    return redirect()->back()->with('success', '圖片上傳成功');
}

}

拿到指定的路徑
修改程式碼

上面也要加上

下指令

修改前端程式碼

下指令啟動


上傳成功的程式碼

<!-- 圖片上傳 Modal -->
<div class="modal fade" id="upload_image_modal" tabindex="-1" role="dialog" aria-labelledby="uploadImageModalLabel" aria-hidden="true">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="uploadImageModalLabel">上傳圖片</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <form id="upload_image_form" method="POST" action="/admin/products/upload-image" enctype="multipart/form-data">
          @csrf
          <input type="hidden" id="product_id" name="product_id" value="">
          <div class="form-group">
            <label for="product_image">選擇圖片</label>
            <input type="file" class="form-control" id="product_image" name="product_image" accept="image/*">
          </div>
          <button type="submit" class="btn btn-primary">上傳</button>
        </form>
      </div>
    </div>
  </div>
</div>

<!-- Excel 匯入 Modal -->
<div class="modal fade" id="import" tabindex="-1" role="dialog" aria-labelledby="importModalLabel" aria-hidden="true">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="importModalLabel">匯入EXCEL</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="Close">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <form id="import_form" method="POST" action="/admin/products/excel/import" enctype="multipart/form-data">
          @csrf
          <div class="form-group">
            <label for="excel">選擇 Excel 檔案</label>
            <input type="file" class="form-control" id="excel" name="excel" accept=".xlsx,.xls,.csv">
          </div>
          <button type="submit" class="btn btn-primary">匯入</button>
        </form>
      </div>
    </div>
  </div>
</div>

<script>
// JavaScript 用於設定產品ID
function openUploadModal(productId) {
    document.getElementById('product_id').value = productId;
    $('#upload_image_modal').modal('show');
}

// 表單提交前驗證
document.getElementById('upload_image_form').addEventListener('submit', function(e) {
    const productId = document.getElementById('product_id').value;
    const imageFile = document.getElementById('product_image').files[0];
    
    if (!productId) {
        e.preventDefault();
        alert('請選擇產品');
        return false;
    }
    
    if (!imageFile) {
        e.preventDefault();
        alert('請選擇圖片檔案');
        return false;
    }
    
    // 檢查檔案大小 (2MB = 2048KB)
    if (imageFile.size > 2048 * 1024) {
        e.preventDefault();
        alert('檔案大小不能超過 2MB');
        return false;
    }
    
    // 檢查檔案類型
    const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif'];
    if (!allowedTypes.includes(imageFile.type)) {
        e.preventDefault();
        alert('請選擇有效的圖片檔案 (JPEG, PNG, GIF)');
        return false;
    }
});

// Excel 匯入表單驗證
document.getElementById('import_form').addEventListener('submit', function(e) {
    const excelFile = document.getElementById('excel').files[0];
    
    if (!excelFile) {
        e.preventDefault();
        alert('請選擇 Excel 檔案');
        return false;
    }
    
    const allowedTypes = [
        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
        'application/vnd.ms-excel', // .xls
        'text/csv' // .csv
    ];
    
    if (!allowedTypes.includes(excelFile.type)) {
        e.preventDefault();
        alert('請選擇有效的 Excel 檔案 (.xlsx, .xls, .csv)');
        return false;
    }
});
</script> 

電商管理平台
http://127.0.0.1:8000/admin/products

https://ithelp.ithome.com.tw/upload/images/20251006/20119035LybC9QaUZJ.pnghttps://ithelp.ithome.com.tw/upload/images/20251006/20119035xE78tihTiK.pnghttps://ithelp.ithome.com.tw/upload/images/20251006/201190357JzWlsfxz5.pnghttps://ithelp.ithome.com.tw/upload/images/20251006/20119035ndAYKaVfgs.png

-- MySQL dump 10.13 Distrib 8.0.41, for Win64 (x86_64)

-- Host: localhost Database: laravel_demo


-- Server version 8.0.41

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT /;
/
!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS /;
/
!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION /;
/
!50503 SET NAMES utf8 /;
/
!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE /;
/
!40103 SET TIME_ZONE='+00:00' /;
/
!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 /;
/
!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 /;
/
!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' /;
/
!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Table structure for table images

DROP TABLE IF EXISTS images;
/*!40101 SET @saved_cs_client = @@character_set_client /;
/
!50503 SET character_set_client = utf8mb4 /;
CREATE TABLE images (
id bigint unsigned NOT NULL AUTO_INCREMENT,
attachable_type varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '來源表',
attachable_id varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '來源表ID',
path varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '路徑',
filename varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '檔案名稱',
created_at timestamp NULL DEFAULT NULL,
updated_at timestamp NULL DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
/
!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table images

LOCK TABLES images WRITE;
/*!40000 ALTER TABLE images DISABLE KEYS /;
INSERT INTO images VALUES (1,'App\Models\Product','2','images/gGzw2Y0y9fyype8tu0KXKrbjsDN9llWLz1hu6px8.png','1x1_和服.png','2025-05-08 15:30:10','2025-05-08 15:30:10'),(8,'App\Models\Product','1','images/2CeP1jTIXxIAywLQkfWektiezeDuQhUlunqb6VZX.jpg','2CeP1jTIXxIAywLQkfWektiezeDuQhUlunqb6VZX.jpg','2025-05-10 04:10:16','2025-05-10 04:10:16');
/
!40000 ALTER TABLE images ENABLE KEYS /;
UNLOCK TABLES;
/
!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE /;
/
!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS /;
/
!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS /;
/
!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT /;
/
!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS /;
/
!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION /;
/
!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

-- Dump completed on 2025-08-29 22:45:10

https://ithelp.ithome.com.tw/upload/images/20251006/20119035XOK38Luq2d.pnghttps://ithelp.ithome.com.tw/upload/images/20251006/201190358lwT3ku8JS.pnghttps://ithelp.ithome.com.tw/upload/images/20251006/20119035uqXYjKUw6X.png

大家明天見~


上一篇
通知按鈕
下一篇
有的四個網址
系列文
Laravel 是甚麼30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言